home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 February: Technology Seed / Mac Tech Seed Feb '97.toast / OpenDoc 1.2b2c1 / Implementation / Utilities / StrHshTb.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1997-02-13  |  18.3 KB  |  683 lines  |  [TEXT/MPS ]

  1. /*
  2.     File:        StrHshTb.cpp
  3.  
  4.     Contains:    xxx put contents here xxx
  5.  
  6.     Owned by:    Jens Alfke
  7.  
  8.     Copyright:    © 1996 by Apple Computer, Inc., all rights reserved.
  9.  
  10.     Change History (most recent first):
  11.  
  12.          <4>     9/27/96    EL        1286530: Consistent error code for remove
  13.                                     as well.
  14.          <3>     9/23/96    EL        1286530: Error cocde inconsistencies
  15.                                     between Find and Exists
  16.          <2>     5/24/96    jpa        1339104: Speed optimizations (mostly for
  17.                                     EditorSetup)
  18.  
  19.     To Do:
  20. */
  21.  
  22. /*
  23.     File:        StrHshTb.cpp
  24.  
  25.     Contains:    Implementation for StringHashTable class.
  26.  
  27.     Owned by:    Nick Pilch
  28.  
  29.     Copyright:    © 1993 - 1995 by Apple Computer, Inc., all rights reserved.
  30.  
  31.     In Progress:
  32.         
  33. */
  34.  
  35. /*
  36.     A simple hash table with chaining. Each hash table entry
  37.     is a HashEntryRec. Each HashEntryRec contains a pointer to start of a
  38.     linked list of EntryNodes. Each EntryNode contains the key, the length of
  39.     key, the value, the length of the value and at least
  40.     one link to another item in the list.
  41.     
  42.     The keys are stored as NULL terminated C-strings for debugging purposes
  43.     even though they may have imbedded NULLs.
  44. */
  45.  
  46. #ifndef _STRHSHTB_
  47. #include "StrHshTb.h"
  48. #endif
  49.  
  50. #ifndef __CTYPE__
  51. #include <ctype.h>
  52. #endif
  53.  
  54. #ifndef __LIMITS__
  55. #include <limits.h>
  56. #endif
  57.  
  58. #ifndef _EXCEPT_
  59. #include "Except.h"
  60. #endif
  61.  
  62. #ifndef __STRING__
  63. #include <string.h>
  64. #endif
  65.  
  66. #ifndef _ODNEW_
  67. #include "ODNew.h"
  68. #endif
  69.  
  70. #ifndef _UTILERRS_
  71. #include "UtilErrs.h"
  72. #endif
  73.  
  74. #pragma segment StringHashTable
  75.  
  76. //==============================================================================
  77. // Local Classes
  78. //==============================================================================
  79.  
  80. struct EntryNode
  81. {
  82.     ODUByte*    key;
  83.     ODULong    keySize;
  84.     ODPtr        value;
  85.     ODULong    valueLen;
  86.     EntryNode*    nextEntry; // == 0 means end of list.
  87. };
  88.  
  89. struct HashEntryRec
  90. {
  91.     EntryNode*    node;        //    pointer to first node
  92. };
  93.  
  94. #if 0
  95.     //OBSOLETE
  96.     struct LinkedNodesIterator
  97.     {
  98.         LinkedNodesIterator(StringHashTable* table, ODULong tableIndex)
  99.                             {fTableIndex = tableIndex; fTable = table;
  100.                                 fCurNode = (table->fTable + tableIndex)->node;}
  101.         EntryNode*    First() {return fCurNode;}
  102.         EntryNode*    Next() {return (fCurNode = fCurNode->nextEntry);}
  103.         ODBoolean    IsNotComplete() {return (fCurNode->nextEntry != 0);}
  104.       private:
  105.         StringHashTable*    fTable;
  106.         ODULong            fTableIndex;
  107.         EntryNode*            fCurNode;
  108.     };
  109. #endif // 0
  110.  
  111. struct LinkedNodesIterator
  112. {
  113.   public:
  114.     inline LinkedNodesIterator(StringHashTable* table, ODULong tableIndex);
  115.     inline    EntryNode*    First();
  116.     inline    EntryNode*    Next();
  117.     inline    void*/*ODBoolean*/    IsNotComplete();
  118.   private:
  119.     StringHashTable*    fTable;
  120.     ODULong            fTableIndex;
  121.     EntryNode*            fCurNode;
  122. };
  123.  
  124. LinkedNodesIterator::LinkedNodesIterator(StringHashTable* table, ODULong tableIndex)
  125. {
  126.     fTableIndex = tableIndex;
  127.     fTable = table;
  128.     fCurNode = (table->GetTable() + tableIndex)->node;
  129. }
  130.  
  131. EntryNode*    LinkedNodesIterator::First()
  132. {
  133.     return fCurNode;
  134. }
  135.  
  136. EntryNode*    LinkedNodesIterator::Next()
  137. {
  138.     return (fCurNode = fCurNode->nextEntry);
  139. }
  140.  
  141. void* /*ODBoolean*/    LinkedNodesIterator::IsNotComplete()
  142. {
  143.     return fCurNode/*!=kODNULL*/;
  144.     
  145.     /* Not comparing against NULL generates more efficient code in CW9. */
  146. }
  147.  
  148.  
  149. //==============================================================================
  150. // StringHashTable
  151. //==============================================================================
  152.  
  153. //------------------------------------------------------------------------------
  154. // StringHashTable::StringHashTable
  155. //------------------------------------------------------------------------------
  156.  
  157. StringHashTable::StringHashTable(ODULong numEntries)
  158. {
  159.     if (numEntries == 0)
  160.         fNumSlots = 1;
  161.     else
  162.         fNumSlots = numEntries;
  163.     fHashFunc = StringHashTable::StdHash;
  164.     fNumEntries = 0;
  165.     fHeapID = kDefaultHeapID;
  166. }
  167.  
  168. //------------------------------------------------------------------------------
  169. // StringHashTable::Initialize
  170. //------------------------------------------------------------------------------
  171.  
  172. void StringHashTable::Initialize(ODMemoryHeapID heapID)
  173. {
  174.     ODULong    tableSize = fNumSlots * sizeof(HashEntryRec);
  175.  
  176.     fHeapID = heapID;
  177.     fTable = (HashEntryRec*)ODNewPtrClear(tableSize, fHeapID);
  178.     if (!fTable)
  179.         THROW(kODErrOutOfMemory);
  180. }
  181.  
  182. //------------------------------------------------------------------------------
  183. // StringHashTable::~StringHashTable
  184. //------------------------------------------------------------------------------
  185.  
  186. StringHashTable::~StringHashTable()
  187. {
  188.     // gotta be careful to delete a node only after we're done with it.
  189.     for (ODULong i = 0; i < fNumSlots; i++)
  190.     {
  191.         LinkedNodesIterator    iter(this, i);
  192.         EntryNode*            someNode;
  193.         EntryNode*            lastNode = 0; // first time through
  194.  
  195.         for (someNode = iter.First();
  196.                 iter.IsNotComplete();
  197.                 someNode = iter.Next())
  198.         {
  199.             if (lastNode)
  200.             {
  201.                 ODDisposePtr((Ptr)lastNode->key);
  202.                 ODDisposePtr((Ptr)lastNode);
  203.             }
  204.             lastNode = someNode;
  205.         }
  206.         if (lastNode)
  207.         {
  208.             ODDisposePtr((Ptr)lastNode->key);
  209.             ODDisposePtr((Ptr)lastNode);
  210.         }
  211.     }
  212.     ODDisposePtr((Ptr)fTable);
  213. }
  214.  
  215. //------------------------------------------------------------------------------
  216. // StringHashTable::Insert
  217. //
  218. //    Hash the key, Map the hash to the range of table index values, insert the
  219. //    hash into the linked list of EntryNodes at that table slot.
  220. //------------------------------------------------------------------------------
  221.  
  222. void StringHashTable::Insert(ODUByte* string, ODULong stringLength,
  223.                                         ODPtr value, ODULong valueLength)
  224. {
  225.     if (stringLength == 0)
  226.         THROW(kODErrInvalidKey);
  227.     if (value == (ODPtr)kODNULL)
  228.         THROW(kODErrIllegalNullInput);
  229.     ODULong tableIndex = this->HashAndMap(string, stringLength);
  230.     this->InsertAtIndex(tableIndex, string, stringLength, value, valueLength);
  231.     ++fNumEntries;
  232. }
  233.  
  234. //------------------------------------------------------------------------------
  235. // StringHashTable::InsertAtIndex
  236. //
  237. //    Insert a hash table node at the the specified index in the table.
  238. //------------------------------------------------------------------------------
  239.  
  240. void StringHashTable::InsertAtIndex(ODULong index, ODUByte* string,
  241.                                                 ODULong stringLength,
  242.                                                 ODPtr value,
  243.                                                 ODULong valueLength)
  244. {
  245.     HashEntryRec*    entry = fTable + index;
  246.     
  247.     // No nodes at this table entry? Stick in the first one.
  248.     if (entry->node == kODNULL)
  249.         entry->node = this->MakeNewNode(string, stringLength, value,
  250.                                         valueLength);
  251.     // Else, need to follow chain to end checking for duplicate keys
  252.     else
  253.     {
  254.         LinkedNodesIterator    iter(this, index);
  255.         EntryNode*            someNode;
  256.         EntryNode*            lastNode;
  257.         ODBoolean            replaceOne = kODFalse;
  258.         ODBoolean            alreadyHadKey = kODFalse;
  259.  
  260.         // Iterate to the end of the list or until we find the key
  261.         for (someNode = iter.First();
  262.                 iter.IsNotComplete();
  263.                 lastNode = someNode, someNode = iter.Next())
  264.         {
  265.             if ((someNode->keySize == stringLength)
  266.                 && (!memcmp((const char*)someNode->key, (const char*)string,
  267.                             (size_t)stringLength)))
  268.             {
  269.                 someNode->value = value;
  270.                 someNode->valueLen = valueLength;
  271.                 alreadyHadKey = kODTrue;
  272.                 break;
  273.             }
  274.         }
  275.         if (!alreadyHadKey)
  276.             // If we got here, we have to create a new node.
  277.             lastNode->nextEntry = MakeNewNode(string, stringLength, value,
  278.                                                 valueLength);
  279.     }
  280. }
  281.  
  282. //------------------------------------------------------------------------------
  283. // StringHashTable::MakeStringFromBytes
  284. //
  285. //    Create a new NULL-terminated C-string from a stream of bytes.
  286. //------------------------------------------------------------------------------
  287.  
  288. ODUByte* StringHashTable::MakeStringFromBytes(ODUByte* string,
  289.                                                     ODULong stringLength)
  290. {
  291.     ODUByte* newString = (ODUByte*)ODNewPtr(stringLength + 1, fHeapID);
  292.     if (!newString)
  293.         THROW(kODErrOutOfMemory);
  294.     ODBlockMove(string, newString, stringLength);
  295.     newString[stringLength] = 0;
  296.     return newString;
  297. }
  298.  
  299. //------------------------------------------------------------------------------
  300. // StringHashTable::MakeNewNode
  301. //
  302. //    Allocate and intialize a new entry node.
  303. //------------------------------------------------------------------------------
  304.  
  305. EntryNode* StringHashTable::MakeNewNode(ODUByte* string,
  306.                                             ODULong stringLength, ODPtr value,
  307.                                             ODULong valueLength)
  308. {
  309.     EntryNode* newNode = (EntryNode*)ODNewPtr(sizeof(EntryNode), fHeapID);
  310.  
  311.     // Convert strings to C-strings and copy
  312.     ODUByte* newString = this->MakeStringFromBytes(string, stringLength);
  313.     
  314.     newNode->key = newString;
  315.     newNode->keySize = stringLength;
  316.     newNode->nextEntry = kODNULL;
  317.     newNode->value = value;
  318.     newNode->valueLen = valueLength;
  319.     
  320.     return newNode;
  321. }
  322.  
  323. //------------------------------------------------------------------------------
  324. // StringHashTable::Remove
  325. //
  326. //    Hash the key, Map the hash to the range of table index values, search the
  327. //    the linked list of EntryNodes for the key, remove the EntryNode in which
  328. //    the key was found.
  329. //------------------------------------------------------------------------------
  330.  
  331. void StringHashTable::Remove(ODUByte* string, ODULong stringLength)
  332. {
  333.     if (stringLength)
  334.     {
  335.         ODULong tableIndex = this->HashAndMap(string, stringLength);
  336.         this->RemoveAtIndex(tableIndex, string, stringLength);
  337.         --fNumEntries;
  338.     }
  339. }
  340.  
  341. //------------------------------------------------------------------------------
  342. // StringHashTable::RemoveAtIndex
  343. //
  344. //    Remove a hash table node at the the specified index in the table.
  345. //------------------------------------------------------------------------------
  346.  
  347. void StringHashTable::RemoveAtIndex(ODULong index, ODUByte* string,
  348.                                                 ODULong stringLength)
  349. {
  350.     HashEntryRec*    entry = fTable + index;
  351.     
  352.     if (entry->node != kODNULL)
  353.     {
  354.         LinkedNodesIterator    iter(this, index);
  355.         EntryNode*            someNode;
  356.         EntryNode*            prevNode = 0; // start of chain
  357.  
  358.         for (someNode = iter.First();
  359.                 iter.IsNotComplete();
  360.                 someNode = iter.Next())
  361.         {
  362.             // look for a match
  363.             if ((someNode->keySize == stringLength)
  364.                 && (!memcmp((const char*)someNode->key, (const char*)string,
  365.                             (size_t)stringLength)))
  366.             {
  367.                 // Special case first node
  368.                 if (prevNode == 0)
  369.                     entry->node = someNode->nextEntry;
  370.                 else
  371.                     prevNode->nextEntry = someNode->nextEntry;
  372.  
  373.                 // Must dispose someNode->key first or else we'll lose its
  374.                 //    reference after disposing the someNode.
  375.                 ODDisposePtr((Ptr)someNode->key);
  376.                 ODDisposePtr((Ptr)someNode);
  377.                 break;
  378.             }
  379.             prevNode = someNode;
  380.         }
  381.     }
  382. }
  383.  
  384. //------------------------------------------------------------------------------
  385. // StringHashTable::Find
  386. //
  387. //    Hash the key, Map the hash to the range of table index values, search the
  388. //    the linked list of EntryNodes for the key. Signal error if not found.
  389. //------------------------------------------------------------------------------
  390.  
  391. ODBoolean StringHashTable::Find(ODUByte* string, ODULong stringLength,
  392.                                     ODPtr* value, ODULong* valueLength)
  393. {
  394.     ODULong        tableIndex = this->HashAndMap(string, stringLength);
  395.     HashEntryRec*    entry = fTable + tableIndex;
  396.     ODBoolean        result = kODFalse;
  397.     
  398.     if (stringLength == 0)
  399.         return kODFalse;
  400.  
  401.     if (entry->node != kODNULL)
  402.     {
  403.         LinkedNodesIterator    iter(this, tableIndex);
  404.         EntryNode*            someNode;
  405.  
  406.         for (someNode = iter.First();
  407.                 iter.IsNotComplete();
  408.                 someNode = iter.Next())
  409.         {
  410.             // match
  411.             if ((someNode->keySize == stringLength)
  412.                 && (!memcmp((const char*)someNode->key, (const char*)string,
  413.                             (size_t)stringLength)))
  414.             {
  415.                 *value = someNode->value;
  416.                 *valueLength = someNode->valueLen;
  417.                 result = kODTrue;
  418.                 break;
  419.             }
  420.         }
  421.     }
  422.  
  423.     return result;
  424. }
  425.  
  426. //------------------------------------------------------------------------------
  427. // StringHashTable::Exists
  428. //
  429. //    Hash the key, Map the hash to the range of table index values, search the
  430. //    the linked list of EntryNodes for the key. Signal error if not found.
  431. //------------------------------------------------------------------------------
  432.  
  433. ODBoolean StringHashTable::Exists(ODUByte* string, ODULong stringLength)
  434. {
  435.     ODULong        tableIndex = this->HashAndMap(string, stringLength);
  436.     HashEntryRec*    entry = fTable + tableIndex;
  437.     ODBoolean        result = kODFalse;
  438.     
  439.     //if (stringLength == 0)
  440.         //THROW(kODErrInvalidKey);
  441.  
  442.     if (stringLength != 0)
  443.     {
  444.         if (entry->node != kODNULL)
  445.         {
  446.             LinkedNodesIterator    iter(this, tableIndex);
  447.             EntryNode*            someNode;
  448.     
  449.             for (someNode = iter.First();
  450.                     iter.IsNotComplete();
  451.                     someNode = iter.Next())
  452.             {
  453.                 // match
  454.                 if ((someNode->keySize == stringLength)
  455.                     && (!memcmp((const char*)someNode->key, (const char*)string,
  456.                                 (size_t)stringLength)))
  457.                 {
  458.                     result = kODTrue;
  459.                     break;
  460.                 }
  461.             }
  462.         }
  463.     }
  464.  
  465.     return result;
  466. }
  467.  
  468. //------------------------------------------------------------------------------
  469. // StringHashTable::StdHash
  470. //
  471. //    Adds all the characters in the string and returns the smallest and largest
  472. //    numbers that they could add up to in hashValueLowerBounds and
  473. //    hashValueUpperBounds.
  474. //------------------------------------------------------------------------------
  475.  
  476. ODULong StringHashTable::StdHash(ODUByte* string,
  477.                                             ODULong stringLength,
  478.                                             ODULong& hashValueLowerBounds,
  479.                                             ODULong& hashValueUpperBounds)
  480. {
  481.     ODULong    result = 0;
  482.  
  483.     for (ODULong i = 0; i < stringLength; i++)
  484.     {
  485.         result += *(string + i);
  486.     }
  487.     hashValueLowerBounds = 0;
  488.     hashValueUpperBounds = 255 * stringLength;
  489.     return result;
  490. }
  491.  
  492. //------------------------------------------------------------------------------
  493. // StringHashTable::MapToTableIndex
  494. //
  495. //    Map hash value in its range to the range 0..(fNumSlots - 1).
  496. //------------------------------------------------------------------------------
  497.  
  498. ODULong StringHashTable::MapToTableIndex(ODULong hash,
  499.                                                 ODULong hashValueLowerBounds,
  500.                                                 ODULong hashValueUpperBounds)
  501. {
  502.     if (hashValueLowerBounds >= hashValueUpperBounds)
  503.         return 0;
  504.     else
  505.     {
  506.         double index = (double)(hash - hashValueLowerBounds)
  507.             / (double)(hashValueUpperBounds - hashValueLowerBounds)
  508.             * (double)(fNumSlots - 1);
  509.         double indexToRound = index + .5;
  510.         return (ODULong)indexToRound;
  511.     }
  512. }
  513.  
  514. //------------------------------------------------------------------------------
  515. // StringHashTable::HashAndMap
  516. //
  517. //    Utility function.
  518. //------------------------------------------------------------------------------
  519.  
  520. ODULong StringHashTable::HashAndMap(ODUByte* string, ODULong stringLength)
  521. {
  522.     ODULong    hashUpperBounds, hashLowerBounds;
  523.  
  524.     ODULong hash = fHashFunc(string, stringLength, hashLowerBounds,
  525.                                 hashUpperBounds);
  526.     return this->MapToTableIndex(hash, hashLowerBounds, hashUpperBounds);
  527. }
  528.  
  529. //------------------------------------------------------------------------------
  530. // StringHashTable::PrintTable
  531. //
  532. //    Testing / Debugging function.
  533. //------------------------------------------------------------------------------
  534.  
  535. #include <stdio.h>
  536.  
  537. void StringHashTable::PrintTable(FILE* outfile)
  538. {
  539.     StringHashTableIterator    iter(this);
  540.     ODUByte*                    string;
  541.     ODULong                    len;
  542.     ODPtr                        value;
  543.     ODULong                    valueLen;
  544.  
  545.     fprintf(outfile, "The table contains this:\n");
  546.  
  547.     for (iter.First(&string, &len, &value, &valueLen);
  548.             iter.IsNotComplete();
  549.             iter.Next(&string, &len, &value, &valueLen))
  550.     {    
  551.         fprintf(outfile, "%s, %d%d\n", string, value, valueLen);
  552.     }
  553.  
  554.     fprintf(outfile, "\n");
  555. }
  556.  
  557. //------------------------------------------------------------------------------
  558. // StringHashTable::PrintDistribution
  559. //
  560. //    Testing / Debugging function.
  561. //------------------------------------------------------------------------------
  562.  
  563. void StringHashTable::PrintDistribution(FILE* outfile)
  564. {
  565.     fprintf(outfile, "The table looks like this:\n");
  566.  
  567.     for (ODULong i = 0; i < fNumSlots; i++)
  568.     {
  569.         fprintf(outfile, "Slot %d:\n", i);
  570.  
  571.         LinkedNodesIterator    iter(this, i);
  572.         EntryNode*            someNode;
  573.  
  574.         for (someNode = iter.First();
  575.                 iter.IsNotComplete();
  576.                 someNode = iter.Next())
  577.         {
  578.             fprintf(outfile, "\t%s\n", someNode->key);
  579.         }
  580.     }
  581.  
  582.     fprintf(outfile, "\n");
  583. }
  584.  
  585. //==============================================================================
  586. // StringHashTableIterator
  587. //==============================================================================
  588.  
  589. //------------------------------------------------------------------------------
  590. // StringHashTableIterator::StringHashTableIterator
  591. //------------------------------------------------------------------------------
  592.  
  593. StringHashTableIterator::StringHashTableIterator(StringHashTable* tb)
  594. {
  595.     fTable = tb;
  596.     fTableIndex = 0;
  597.     fCurNode = kODNULL;
  598.     fDone = kODFalse;
  599. }
  600.  
  601. //------------------------------------------------------------------------------
  602. // StringHashTableIterator::First
  603. //
  604. //    Use GetNext to find first. Set flag if we don't find an entry.
  605. //------------------------------------------------------------------------------
  606.  
  607. void StringHashTableIterator::First(ODUByte** string, ODULong* len,
  608.                                             ODPtr* value,
  609.                                             ODULong* valueLength)
  610. {
  611.     if (!this->GetNext(string, len, value, valueLength))
  612.     {
  613. //        *len  = 0;
  614. //        *string = (ODUByte*)kODNULL;
  615.         fDone = kODTrue;
  616.     }
  617. }
  618.  
  619. //------------------------------------------------------------------------------
  620. // StringHashTableIterator::Next
  621. //
  622. //    Use GetNext. Set flag if we don't find an entry.
  623. //------------------------------------------------------------------------------
  624.  
  625. void StringHashTableIterator::Next(ODUByte** string, ODULong* len,
  626.                                             ODPtr* value,
  627.                                             ODULong* valueLength)
  628. {
  629.     if (!this->GetNext(string, len, value, valueLength))
  630.     {
  631. //        *len  = 0;
  632. //        *string = (ODUByte*)kODNULL;
  633.         fDone = kODTrue;
  634.     }
  635. }
  636.  
  637. //------------------------------------------------------------------------------
  638. // StringHashTableIterator::GetNext
  639. //
  640. // kODTrue is returned if an entry was found, kODFalse otherwise. The
  641. //    values pointed to by the parameters are undefined if kODFalse is
  642. //    returned.
  643. //    We can record the index of the last hash bucket we were looking in, but we
  644. //    can't record the index into the linked list at that bucket. So we keep a
  645. //    pointer to the last node found and look for it the next time we iterate
  646. //    through that list.
  647. //------------------------------------------------------------------------------
  648.  
  649. ODBoolean StringHashTableIterator::GetNext(ODUByte** string, ODULong* len,
  650.                                                 ODPtr* value,
  651.                                                 ODULong* valueLength)
  652. {
  653.     for (ODULong i = fTableIndex; i < fTable->GetNumSlots(); i++)
  654.     {
  655.         LinkedNodesIterator    iter(fTable, i);
  656.         EntryNode*            someNode;
  657.         ODBoolean            pastLastNode = fCurNode ? kODFalse : kODTrue;
  658.  
  659.         for (someNode = iter.First();
  660.                 iter.IsNotComplete();
  661.                 someNode = iter.Next())
  662.         {
  663.             if ((i == fTableIndex) && (someNode == fCurNode))
  664.             {
  665.                 pastLastNode = kODTrue;
  666.                 continue;
  667.             }
  668.             if (pastLastNode || (i != fTableIndex))
  669.             {
  670.                 fTableIndex = i;
  671.                 fCurNode = someNode;
  672.                 *string = someNode->key;
  673.                 *len = someNode->keySize;
  674.                 *value = someNode->value;
  675.                 *valueLength = someNode->valueLen;
  676.                 return kODTrue;
  677.             }
  678.         }
  679.     }
  680.     return kODFalse;
  681. }
  682.  
  683.